﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Messaging;
using PServiceBus.Core.Runtime;
using PServiceBus.Core.Logger;
using PServiceBus.Core.Interface;
using System.Collections;
using System.Collections.Concurrent;

namespace PServiceBus.MSMQ {
    public class Provider<TObject> : IHaveEndpoint, IObjectProvider<TObject> where TObject : class {
        private object _lockObject = new object();
        private static ConcurrentDictionary<string, object> _providers =
            new ConcurrentDictionary<string, object>();
        private bool _containQueueName = false;

        public static Provider<TObject> GetProvider(string endpoint, bool appendTypeNameToEndpoint = false,
            string endpointAlias = null) {
            var key = String.Concat(endpoint, endpointAlias ?? string.Empty);
            return _providers.GetOrAdd(key, 
                k => new Provider<TObject> { Endpoint = endpoint, EndpointAlias = endpointAlias,
                    AppendTypeNameToEndpoint = appendTypeNameToEndpoint,
                    IDFunc = o => Guid.NewGuid()
                })
                as Provider<TObject>;
        }

        private void Initialize() {
            if (!_containQueueName) {
                Endpoint = (AppendTypeNameToEndpoint ? String.Format("{0}{1}", Endpoint,
                    typeof(TObject).FullName.Replace(".", "_")) : Endpoint) + (EndpointAlias ?? string.Empty);
                CreateQueue();
                _containQueueName = true;
            }
        }

        public bool Exists(TObject obj) {
            return GetObjectByID(IDFunc(obj).ToString(), false) != null;
        }

        public TObject Get(Guid id) {
            return GetObjectByID(id.ToString(), false);
        }

        public bool Delete(TObject obj) {
            return GetObjectByID(IDFunc(obj).ToString(), true) != null;
        }

        private void Query(Action<MessageQueue> action) {
            Initialize();
            lock (_lockObject)
                using (var queue = GetMessageQueue())
                    action(queue);
        }

        

        private MessageQueue GetMessageQueue() {
            Initialize();
            return new MessageQueue(Endpoint, false, true) { Formatter = new BinaryMessageFormatter() };
        }
        
        private void CreateQueue() {
            try {
                if (!MessageQueue.Exists(Endpoint)) MessageQueue.Create(Endpoint);
            }
            catch {  }
        }
        
        private TObject GetObjectByID(string id, bool deleteObjectOnRead) {
            var obj = default(TObject);
            try {
                lock (_lockObject) {
                    using (var queue = GetMessageQueue()) {
                        var msgs = queue.GetMessageEnumerator2();
                        while (msgs.MoveNext()) {
                            var msg = msgs.Current;
                            msg.Formatter = new BinaryMessageFormatter();
                            var current = msg.Body as TObject;
                            if (current != null && (id == null || (id != null && msg.Label == id))) {
                                if (deleteObjectOnRead) msgs.RemoveCurrent();
                                obj = current;
                                break;
                            }
                        }
                    }
                }
            }
            catch {
            }
            return obj;
        }

        #region IObjectProvider<TObject> Members

        public bool Refresh(TObject obj) {
            throw new NotImplementedException();
        }

        public bool Add(IEnumerable<TObject> list) {
            Query(queue =>
            {
                foreach (var obj in list) {
                    var msg = new Message(obj,
                    new BinaryMessageFormatter());
                    msg.Label = IDFunc(obj).ToString();
                    queue.Send(msg);
                }
            });
            return true;
        }

        public bool Add(TObject obj) {
            Query(queue =>
            {
                var msg = new Message(obj,
                new BinaryMessageFormatter());
                msg.Label = IDFunc(obj).ToString();
                queue.Send(msg);
            });
            return true;
        }

        public void Clear() {
            Query(queue => queue.Purge());
        }

        public void Delete() {
            Initialize();
            MessageQueue.Delete(Endpoint);
        }
        
        public bool Delete(IEnumerable<TObject> list) {
            var success = true;
            foreach (var obj in list)
                success &= GetObjectByID(IDFunc(obj).ToString(), true) != null;
            return success;
        }

        public bool Delete(IEnumerable<Func<TObject, bool>> conditions) {
            var success = true;
            foreach (var condition in conditions) {
                var deleteObj = this.FirstOrDefault(obj => obj != null && condition(obj));
                success &= deleteObj != null
                    && GetObjectByID(IDFunc(deleteObj).ToString(), true) != null;
            }
            return success;
        }

        public bool Delete(Func<TObject, bool> condition) {
            var deleteObj = this.FirstOrDefault(obj => obj != null && condition(obj));
            return deleteObj != null
                && GetObjectByID(IDFunc(deleteObj).ToString(), true) != null;   
        }

        public bool Exists(Func<TObject, bool> condition) {
            return Get(condition) != null;
        }

        public TObject Get(Func<TObject, bool> condition) {
            return this.FirstOrDefault(obj => condition(obj));
        }

        public string Endpoint { get; set; }

        public string EndpointAlias { get; set; }

        public bool AppendTypeNameToEndpoint { get; set; }

        public bool DeleteObjectOnRead { get; set; }

        public Func<TObject, Guid> IDFunc { get; set; }
        #endregion

        #region IEnumerable<TObject> Members

        public IEnumerator<TObject> GetEnumerator() {
            lock (_lockObject) {
                using (var queue = GetMessageQueue()) {
                    var msgs = queue.GetMessageEnumerator2();
                    while (msgs.MoveNext()) {
                        var current = default(TObject);
                        MethodHelper.Try(() =>
                        {
                            var msg = DeleteObjectOnRead ? msgs.RemoveCurrent() : msgs.Current;
                            msg.Formatter = new BinaryMessageFormatter();
                            current = msg.Body as TObject;
                        });
                        if (current != null) yield return current;
                        if (DeleteObjectOnRead) msgs.Reset();
                    }
                };
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        #endregion

        #region IDisposable Members

        public void Dispose() {
        }

        #endregion
    }
}
